<?php
/**
 * @license http://www.gnu.org/licenses/lgpl.html LGPL
 * @author Harlan Iverson <h dot iverson at gmail dot com>
 * @copyright Copyright (c) 2006, Harlan Iverson.
 * @version $Id$
 */

require_once( "PhpFaces/Context/FacesContext.php" );
require_once( "PhpFaces/Component/Component.php" );
require_once( "PhpFaces/Event/Event.php" );
require_once( "PhpFaces/Event/Listener.php" );
require_once( "PhpFaces/EL/EL.php" );
require_once( "PhpFaces/Lang/Lang.php" );



abstract class PhpFaces_Component_UIComponent {
	public abstract function encodeBegin( $facesContext );
	public abstract function encodeEnd( $facesContext );
	public abstract function encodeChildren( $facesContext );
	public abstract function isRendered();
	public abstract function setRendered( $isRendered );
	public abstract function getRendersChildren();
	
	public abstract function getRenderer( PhpFaces_Context_FacesContext $facesContext );
	public abstract function getRendererType();
	public abstract function setRendererType( $type );
	
	/**
	 * Search for and return the UIComponent with an id  that matches the 
	 * specified search expression (if any), according to the algorithm described below.

	 * For a method to find a component given a simple clientId, see invokeOnComponent(
	 * javax.faces.context.FacesContext, java.lang.String, javax.faces.component.ContextCallback).

	 * Component identifiers are required to be unique within the scope of the 
	 * closest ancestor NamingContainer that encloses this component (which might 
	 * be this component itself). If there are no NamingContainer components in 
	 * the ancestry of this component, the root component in the tree is treated 
	 * as if it were a NamingContainer, whether or not its class actually 
	 * implements the NamingContainer interface.
	 * 
	 * A search expression consists of either an identifier (which is matched 
	 * exactly against the id property of a UIComponent, or a series of such 
	 * identifiers linked by the NamingContainer.SEPARATOR_CHAR character 
	 * value. The search algorithm should operates as follows, though 
	 * alternate alogrithms may be used as long as the end result is the same:
	 *     * Identify the UIComponent that will be the base for searching, by stopping as soon as one of the following conditions is met:
	 *           * If the search expression begins with the the separator character (called an "absolute" search expression), the base will be the root UIComponent of the component tree. The leading separator character will be stripped off, and the remainder of the search expression will be treated as a "relative" search expression as described below.
	 *           * Otherwise, if this UIComponent is a NamingContainer it will serve as the basis.
	 *           * Otherwise, search up the parents of this component. If a NamingContainer is encountered, it will be the base.
 	 *           * Otherwise (if no NamingContainer is encountered) the root UIComponent will be the base.
	 *     * The search expression (possibly modified in the previous step) is now a "relative" search expression that will be used to locate the component (if any) that has an id that matches, within the scope of the base component. The match is performed as follows:
	 *           * If the search expression is a simple identifier, this value is compared to the id property, and then recursively through the facets and children of the base UIComponent (except that if a descendant NamingContainer is found, its own facets and children are not searched).
	 *           * If the search expression includes more than one identifier separated by the separator character, the first identifier is used to locate a NamingContainer by the rules in the previous bullet point. Then, the findComponent() method of this NamingContainer will be called, passing the remainder of the search expression.
	 * 
	 */
	public abstract function findComponent( $expr );
	/** @return PhpFaces_Component_UIComponent parent*/
	public abstract function getParent();
	/** @return PhpFaces_Lang_ListObject<PhpFaces_Component_UIComponent> a list of children */
	public abstract function getChildren();
	
	//public abstract function getFacet( $name );
	//public abstract function getFacets();
	//public abstract function getFacetsAndChildren();
	
	//public abstract function getClientId();
	public abstract function getId();
	public abstract function setId( $id );
	
	protected abstract function getViewRoot();
	protected abstract function getChildById( $id );
	protected abstract function getNamingContainer();
	
	
	public abstract function processDecodes( PhpFaces_Context_FacesContext $facesContext );
	public abstract function queueEvent( PhpFaces_Event_FacesEvent $event );
	
	public static function ToString( PhpFaces_Component_UIComponent $c ) {
		return get_class( $c )." - ".$c->getId();
	}

}




abstract class PhpFaces_Component_UIComponentBase extends PhpFaces_Component_UIComponent {

	const LOGGER = "PhpFaces.Component.UI.UIComponentBase";
	private $children;

	public function getChildren() {
		if( $this->children == null ) {
			$this->children = new PhpFaces_Lang_ListObject();
		}
		return $this->children;
	}
	
	public function getId() {
		if( $this->id == null ) {
			$this->id = $this->getFacesContext()->getViewRoot()->createUniqueId();
			$logger = LoggerManager::getLogger( self::LOGGER );
			
			$logger->warn( "generated unique ID for component ".$this->id );
		}
		return $this->id;
	}

	
	/**
	 * @todo I'm not sure this implementation is right
	 */
	public function findComponent( $expr ) {

		$logger = LoggerManager::getLogger( self::LOGGER . ".findComponent" );
		$logger->debug( "looking for component: $expr");
			
		try {
			// split the expression into two parts to find base and remaining path
			$exprParts = explode( PhpFaces_Component_NamingContainer::SEPARATOR_CHAR, $expr, 2 );
			$numberOfParts = count( $exprParts );
			
			$logger->debug( "number of parts: $numberOfParts");
			
			// bad expression
			if( $numberOfParts == 0 ) {
				throw new Exception( "WTF?" );
			}
		
			$namingContainer = null;
			// if there are two parts and the first is blank,
			// then the search must be absolute (root)
			if( $exprParts[0] === "" && $numberOfParts == 2 ) {
				$namingContainer = $this->getViewRoot();
			// otherwise find the nearest naming container
			} else {
				$namingContainer = $this->getNamingContainer();
			}
			
			$logger->debug( "naming container: ".PhpFaces_Component_UIComponent::ToString( $namingContainer ) );
			
			
			
			// if there are two parts and the second part contains a separator
			// then call find component recusively
			if( $numberOfParts == 2 ) {
				$logger->debug( "recursing... ".$exprParts[1] );
				$child = $namingContainer->findComponent( $exprParts[1] );
			// otherwise if there are two parts but the second is at its
			// final level, look for an id within this component...
			// I think we might want to test if this component is a naming container
			} else {
				$logger->debug( "getChildById ".$exprParts[0] );
				$child = $this->getChildById( $exprParts[0] );
			}
			
			$logger->debug( "child = ".PhpFaces_Component_UIComponent::ToString( $child ) );
			
			return $child;
		} catch( Exception $e ) {
			return null;
		}
	}
	
	/**
	 * Find the nearest naming container. See findComponent() for more info
	 */
	protected function getNamingContainer() {
		if( $this instanceof PhpFaces_Component_NamingContainer ) {
			return $this;
		} else {
			$parent = $this->getParent();
			if( $parent == null ) {
				return $this;
			} else {
				return $parent->getNamingContainer();
			}
		}
	}
	
	/*
	public function findComponentOLD( $expr ) {
		
		try {
			$exprParts = explode(PhpFaces_Component_NamingContainer::SEPARATOR_CHAR,$expr,2);
			$numberOfParts = count( $exprParts );
			
			if( $numberOfParts == 0 ) {
				throw new Exception( "WTF?" );
			}
		
			$child = null;
			
			// first we get the child on the left side of separator,
			// if it is empty use the view root...
			// otherwise, use the child we find
			if( $exprParts[0] === "" && $numberOfParts == 2 ) {
				$child = $this->getViewRoot();
			} else {
				$child = $this->getChildById( $exprParts[0] );
			}

			// then we call findComponent on that child with the rest of the expression
			if( $numberOfParts == 2 && $exprParts[1] != "" ) {
				$child = $child->findComponent( $exprParts[1] );
			} else {
				throw new Exception( "WTF?" );
			}
			
			return $child;
		} catch( Exception $e ) {
			return null;
		}
	}
	*/
	
	protected function getChildById( $id ) {
		// FIXME list should work as array
		foreach( $this->getChildren()->asArray() as $child ) {
			if( $child->getId() == $id ) {
				return $child;
			}
		}
		throw new Exception( "ChildNotFoundException" );
	}
	
	/** a php specific method of getting view root. 
	 * see UIViewRoot::getViewRoot(), it returns itself. */
	protected function getViewRoot() {
		return $this->getParent()->getViewRoot();
	}
	
	// ================================= Renderer Stuff =============================
	public function encodeBegin( $facesContext ) {
		$renderer = $this->getRenderer( $facesContext );
		if( $renderer != null ) {
			$renderer->encodeBegin( $facesContext, $this );
		}
	}
	public function encodeEnd( $facesContext ) {
		$renderer = $this->getRenderer( $facesContext );
		if( $renderer != null ) {
			$renderer->encodeEnd( $facesContext, $this );
		}
	}
	public function encodeChildren( $facesContext ) {
		$renderer = $this->getRenderer( $facesContext );
		if( $renderer != null ) {
			$renderer->encodeEnd( $facesContext, $this );
		}
	}
	
	public function getRendersChildren() {
		$renderer = $this->getRenderer( $this->getFacesContext() );
		if( $renderer != null ) {
			return $renderer->getRendersChildren();
		} else {
			return false;
		}
	}

	
	public function getRenderer( PhpFaces_Context_FacesContext $facesContext ) {
		$rendererType = $this->getRendererType();
		if( $rendererType == null ) { return null; }
		$renderKitId = $facesContext->getViewRoot()->getRenderKitId();
		$rkf = PhpFaces_Render_RenderKitFactory::getInstance();
		$renderKit = $rkf->getRenderKit( $facesContext, $renderKitId );
		$renderer = $renderKit->getRenderer( $this->getFamily(), $rendererType );
		if( $renderer == null ) {
			$logger = LoggerManager::getLogger( self::LOGGER );
			$logger->warn( "Component of type [".get_class($this)."] tried to use non-existant renderer: family=".$this->getFamily(), +", type=".$rendererType );
		}
		return $renderer;
	}

	protected function getFacesContext() {
		return PhpFaces_Context_FacesContext::getCurrentInstance();
	}
	
	
	
	// ================================= Event Queue =============================

	/**
	 * Pass an event up the chain to the UIViewRoot where it is stord in a queue.
	 * If the parent is null then an IllegalStateException is thrown.
	 */
	public function queueEvent( PhpFaces_Event_FacesEvent $event ) {
		// if( $event == null ) { throw new NullPointerException( "event" ); }	
		$parent = $this->getParent();
		if( $parent == null ) {
			// throw new IllegalStateException("component is not a descendant of a UIViewRoot");	
		}
		$parent->queueEvent( $event );
	}
	
	
	// ================================= Lifecycle Processing Stuff =============================
	/**
	 * Decode request value for all children and facets, and then decode for
	 * self. 
	 * 
	 * In the event of failure (Exception), the default behavior is to catch,
	 * call renderResponse and rethrow.
	 */
	public function processDecodes( PhpFaces_Context_FacesContext $facesContext ) {
	
		if( $facesContext == null ) {
			// throw new NullPointerException( "facesContext" );	
		}
	
		// we're not rendered, so it's a waste to process
		// decodes for self or children.
		if( !$this->isRendered() ) { return; }
	
		$children = $this->getChildrenAndFacets();
		foreach( $children as $childOrFacet ) {
			$childOrFacet->processDecodes( $facesContext );
		}
		
		try {
			$this->decode( $facesContext );	
		} catch( Exception $e ) {
			$facesContext->renderResponse();
			throw $e;	
		}
	}
	
	
	
// ---- Begin Generated Code For PhpFaces_Component_UIComponentBase - DO NOT MODIFY ----
	 /** @type boolean */
	private $transient;
	 /** @type java.lang.String */
	private $id;
	 /** @type javax.faces.component.UIComponent */
	private $parent;
	 /** @type boolean */
	private $rendered;
	 /** @type java.lang.String */
	private $rendererType;
	 /** @type boolean */
	public function isTransient() {
		return $this->transient;
	}
	 /** @return boolean */
	public function setTransient( $newValue ) {
		$this->transient = $newValue;
	}
	 /** @return java.lang.String */
	public function setId( $newValue ) {
		$this->id = $newValue;
	}
	 /** @type javax.faces.component.UIComponent */
	public function getParent() {
		return $this->parent;
	}
	 /** @return javax.faces.component.UIComponent */
	public function setParent( $newValue ) {
		$this->parent = $newValue;
	}
	 /** @type boolean */
	public function isRendered() {
		// TODO take default value into account
		return $this->rendered != null ? $this->rendered : true;
	}
	 /** @return boolean */
	public function setRendered( $newValue ) {
		$this->rendered = $newValue;
	}
	 /** @type java.lang.String */
	public function getRendererType() {
		return $this->rendererType;
	}
	 /** @return java.lang.String */
	public function setRendererType( $newValue ) {
		$this->rendererType = $newValue;
	}
// ---- End Generated Code For PhpFaces_Component_UIComponentBase - DO NOT MODIFY ----

}



class PhpFaces_Component_UIViewRoot extends PhpFaces_Component_UIComponentBase {
	const COMPONENT_TYPE = "javax.faces.ViewRoot";
	const COMPONENT_FAMILY = "javax.faces.ViewRoot";

	private $viewId;
	
	public function __construct() {
		// no renderer
	}
	
	public function getFamily() {
		return self::COMPONENT_FAMILY;
	}
	
	public function getParent() {
		return null;
	}
	
	public function setParent( PhpFaces_Component_UIComponent $parent ) {
		// TODO throw some kind of exception?
	}
	
	public function getViewId() {
		return $this->viewId;
	}
	
	public function setViewId( $viewId ) {
		$this->viewId = $viewId;
	}
	
	private $idIncrement = 1;
	public function createUniqueId() {
		$uniq = "id".$this->idIncrement++;
		return $uniq;
	}
	
	protected function getViewRoot() {
		return $this;
	}
	
	private $renderKitId;
	public function getRenderKitId() {
		return $this->renderKitId;
	}
	
	public function setRenderKitId( $id ) {
		$this->renderKitId = $id;
	}
	
	private $events;
	public function queueEvent( PhpFaces_Event_FacesEvent $event ) {
		if( !is_array($this->events) ) {
			$this->events = array();	
		}
		$this->events[] = $event;
	}
	
	private function clearEvents() {
		$this->events = null;	
	}
	
	private function clearEventsIfNecessary( PhpFaces_Context_FacesContext $facesContext ) {
		if( $facesContext->getRenderResponse() || $facescontext->getResponseComplete() ) {
			$this->clearEvents();	
		}	
	}
	
	public function processDecodes( PhpFaces_Context_FacesContext $facesContext ) {
		//if( $facesContext == null ) { throw new NullPointerException( "facesContext" ); }	
		parent::processDecodes( $facesContext );
		$this->broadcastForPhase( PhpFaces_PhaseId::APPLY_REQUEST_VALUES );
		$this->clearEventsIfNecessary( $facesContext );
	}
	
}

class PhpFaces_Component_UIOutput extends PhpFaces_Component_UIComponentBase {
// ---- Begin Generated Code For PhpFaces_Component_UIOutput - DO NOT MODIFY ----
	const COMPONENT_TYPE = "javax.faces.Output";
	const COMPONENT_FAMILY = "javax.faces.Output";
	const RENDERER_TYPE = "javax.faces.Text";
	public function __construct() {
		$this->setRendererType( self::RENDERER_TYPE );
	}
	// TODO take this into account in the generator
	public function getFamily() {
		return self::COMPONENT_FAMILY;
	}
	 /** @type javax.faces.convert.Converter */
	private $converter;
	 /** @type java.lang.Object */
	private $value;
	 /** @type javax.faces.convert.Converter */
	public function getConverter() {
		return $this->converter;
	}
	 /** @return javax.faces.convert.Converter */
	public function setConverter( $newValue ) {
		$this->converter = $newValue;
	}
	 /** @type java.lang.Object */
	public function getValue() {
		return $this->value;
	}
	 /** @return java.lang.Object */
	public function setValue( $newValue ) {
		$this->value = $newValue;
	}
// ---- End Generated Code For PhpFaces_Component_UIOutput - DO NOT MODIFY ----
}




class PhpFaces_Component_UIInput extends PhpFaces_Component_UIOutput implements PhpFaces_Component_ValueHolder {
	const COMPONENT_TYPE = "javax.faces.Input";
	const COMPONENT_FAMILY = "javax.faces.Input";
	const DEFAULT_RENDERER_TYPE = "javax.faces.Text";
	
	private $value;
	
	
	public function __construct() {
		// parent::__construct(); ?
		$this->setRendererType( self::COMPONENT_TYPE );
	}
	
	public function getFamily() {
		return self::COMPONENT_FAMILY;
	}
	
	public function getValue() {
		if( $this->value instanceof EL_ValueBinding ) {
			return $this->value->getValue();
		} else {
			return $this->value;
		}
	}
	public function getLocalValue() {
		return $this->value;
	}
	public function setValue( $value ) {
		$this->value = $value;
	}
}


class PhpFaces_Component_UIForm extends PhpFaces_Component_UIComponentBase {
// ---- Begin Generated Code For PhpFaces_Component_UIForm - DO NOT MODIFY ----
	const COMPONENT_TYPE = "javax.faces.Form";
	const COMPONENT_FAMILY = "javax.faces.Form";
	const RENDERER_TYPE = "javax.faces.Form";
	public function __construct() {
		$this->setRendererType( self::RENDERER_TYPE );
	}
	// TODO take this into account in generator
	public function getFamily() {
		return self::COMPONENT_FAMILY;
	}
	 /** @type boolean */
	private $submitted;
	 /** @type boolean */
	public function isSubmitted() {
		return $this->submitted;
	}
	 /** @return boolean */
	public function setSubmitted( $newValue ) {
		$this->submitted = $newValue;
	}
// ---- End Generated Code For PhpFaces_Component_UIForm - DO NOT MODIFY ----
}

class PhpFaces_Component_UICommand extends PhpFaces_Component_UIComponentBase implements PhpFaces_StateHolder {
// ---- Begin Generated Code For PhpFaces_Component_UICommand - DO NOT MODIFY ----
	const COMPONENT_TYPE = "javax.faces.Command";
	const COMPONENT_FAMILY = "javax.faces.Command";
	const RENDERER_TYPE = "javax.faces.Button";
	public function __construct() {
		$this->setRendererType( self::RENDERER_TYPE );
	}
	// TODO take this into account in generator
	public function getFamily() {
		return self::COMPONENT_FAMILY;
	}
	 /** @type javax.faces.el.MethodBinding */
	private $action;
	 /** @type javax.faces.el.MethodBinding */
	private $actionListener;
	 /** @type boolean */
	private $immediate;
	 /** @type java.lang.Object */
	private $value;
	 /** @type javax.faces.el.MethodBinding */
	public function getAction() {
		return $this->action;
	}
	 /** @return javax.faces.el.MethodBinding */
	public function setAction( $newValue ) {
		$this->action = $newValue;
	}
	 /** @type javax.faces.el.MethodBinding */
	public function getActionListener() {
		return $this->actionListener;
	}
	 /** @return javax.faces.el.MethodBinding */
	public function setActionListener( $newValue ) {
		$this->actionListener = $newValue;
	}
	 /** @type boolean */
	public function isImmediate() {
		return $this->immediate;
	}
	 /** @return boolean */
	public function setImmediate( $newValue ) {
		$this->immediate = $newValue;
	}
	 /** @type java.lang.Object */
	public function getValue() {
		return $this->value;
	}
	 /** @return java.lang.Object */
	public function setValue( $newValue ) {
		$this->value = $newValue;
	}
	function saveState( PhpFaces_Context_FacesContext $context ) {
		$state = array();
		$state[0] = parent::saveState( $context );
		$state[1] = $this->action;
		$state[2] = $this->actionListener;
		$state[3] = $this->immediate;
		$state[4] = $this->value;
		return $state;
	}
	function restoreState( PhpFaces_Context_FacesContext $context, $state ) {
		parent::restoreState( $context, $state[0] );
		$this->action = $state[1];
		$this->actionListener = $state[2];
		$this->immediate = $state[3];
		$this->value = $state[4];
	}
// ---- End Generated Code For PhpFaces_Component_UICommand - DO NOT MODIFY ----
}

?>